package pl.edu.agh.pros.internal.proxy;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.agh.pros.connector.IServerConnector;
import pl.edu.agh.pros.internal.interf.Disposable;
import pl.edu.agh.pros.prop.Props;
import pl.edu.agh.pros.renewal.ExecutionTask;
import pl.edu.agh.pros.renewal.PeriodicalExecutorFactory;
import pl.edu.agh.pros.xmpp.XMPPManager;

//TODO sth that clears rooms with no when proxy dispatcher not cleaned
// TODO sth that periodically clears messageReceivers and instancesMap
public class ProxyDispatcher implements Disposable {

	private static final double REGISTRATION_DIVIDER = 2.1;
	private static final Logger logger = LoggerFactory.getLogger(ProxyDispatcher.class);
	private final IServerConnector serverConnector;
	private final XMPPManager xmppManager;
	private final boolean isProvider;
	private final String providerXmppAddres;
	private final String devId;
	private final String appKey;

	private Map<String, IMessageReceiver> messageReceivers;

	public ProxyDispatcher(IServerConnector serverConnector, XMPPManager xmppManager, boolean isProvider,
			String providerXmppAddres, String devId, String appKey) throws XMPPException {
		this.serverConnector = serverConnector;
		this.xmppManager = xmppManager;
		this.isProvider = isProvider;
		this.providerXmppAddres = providerXmppAddres;
		this.devId = devId;
		this.appKey = appKey;
		this.messageReceivers = new HashMap<String, IMessageReceiver>();
		this.createPacketListener();
	}

	public void registerMessageReceiver(String type, IMessageReceiver messageReceiver) {
		messageReceivers.put(type, messageReceiver);
	}

	public void unregisterMessageReceiver(String type) {
		messageReceivers.remove(type);
	}

	public MultiUserChat joinMultiUserChat(IMessageReceiver messageReceiver, String topic) {
		String roomName = serverConnector.getRoomName(appKey, topic);
		if (isProvider && roomName != null) {
			return createChat(messageReceiver, topic);
		} else {
			return joinChat(messageReceiver, topic);
		}
	}

	public void sendXmppMessage(Message message, String receiverXmppAddress) {
		message.setFrom(xmppManager.getLoggedUser());
		xmppManager.sendMessage(message, receiverXmppAddress);
	}

	public String getXmppAddressForDevice(String devId) {
		if (devId == null) {
			return providerXmppAddres;
		}
		return serverConnector.getXmppAddress(appKey, devId);
	}

	@Override
	public void dispose() {
		// unregister topics, .....
		// TODO Auto-generated method stub

	}

	public boolean isConnected() {
		return xmppManager.isConnected();
	}

	private MultiUserChat createChat(IMessageReceiver messageReceiver, final String topic) {
		try {
			final String roomName = UUID.randomUUID().toString() + topic.replace("/", "x"); //$NON-NLS-1$//$NON-NLS-2$
			MultiUserChat muc = xmppManager.createMultiUserChat(roomName, devId);
			if (messageReceiver != null) {
				createMultiUserChatListener(messageReceiver, muc);
			}
			Long topicRegistrationDuration = serverConnector.registerTopic(appKey, topic, roomName);
			PeriodicalExecutorFactory.getPeriodicalExecutor((long) (topicRegistrationDuration / REGISTRATION_DIVIDER))
					.addTask(new ExecutionTask() {
						@Override
						public void execute() {
							serverConnector.registerTopic(appKey, topic, roomName);
							logger.debug("Topic registration renewed [" + topic + ", " + roomName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}
					}, 0);
			return muc;
		} catch (XMPPException e) {
			logger.error("Can not create MultiUserChat.", e); //$NON-NLS-1$
			return null;
		}
	}

	private MultiUserChat joinChat(IMessageReceiver messageReceiver, String topic) {
		try {
			String roomName = serverConnector.getRoomName(appKey, topic);
			if (roomName == null) {
				logger.error("The room for specified topic does not exist"); //$NON-NLS-1$
				return null;
			}
			MultiUserChat muc = xmppManager.joinMultiUserChat(roomName, devId);
			if (messageReceiver != null) {
				createMultiUserChatListener(messageReceiver, muc);
			}
			return muc;
		} catch (XMPPException e) {
			logger.error("Can not join MultiUserChat.", e); //$NON-NLS-1$
			return null;
		}
	}

	private void createMultiUserChatListener(final IMessageReceiver messageReceiver, MultiUserChat muc) {
		muc.addMessageListener(new PacketListener() {
			@Override
			public void processPacket(Packet packet) {
				if (packet instanceof Message) {
					messageReceiver.onMessage((Message) packet);
				}
			}
		});
	}

	private void createPacketListener() {
		PacketListener packetListener = new PacketListener() {
			public void processPacket(Packet packet) {
				if (packet instanceof Message) {
					Message message = (Message) packet;
					Object key = message.getProperty(Props.XMPP_MSG_TYPE);
					IMessageReceiver messageReceiver = messageReceivers.get(key);
					if (messageReceiver != null) {
						if (messageReceiver.isRunning()) {
							try {
								messageReceiver.onMessage(message);
							} catch (Exception e) {
								logger.debug("An error occured: ", e); //$NON-NLS-1$
							}
						} else {
							messageReceivers.remove(key);
						}
					}
				}
			}
		};
		xmppManager.addPacketListener(packetListener);
	}
}
